<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-element.js"></script>
  <script type="module" src="../../lib/utils/flattened-nodes-observer.js"></script>
</head>
<body>

  <dom-module id='test-self-observe'>
    <template>
      <slot id="slot"></slot>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import { FlattenedNodesObserver } from '../../lib/utils/flattened-nodes-observer.js';
class TestSelfObserve extends PolymerElement {
  static get is() { return 'test-self-observe';}

  connectedCallback() {
    super.connectedCallback();
    this._observer = new FlattenedNodesObserver(this, (info) => {
      this.info = info;
    });
  }
  disconnectedCallback() {
    super.disconnectedCallback();
    this._observer.disconnect();
  }
}
customElements.define(TestSelfObserve.is, TestSelfObserve);
</script>
  </dom-module>

  <dom-module id='test-static'>
    <template>
      <div>static</div>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class TestStatic extends PolymerElement {
  static get is() { return 'test-static'; }
}
customElements.define(TestStatic.is, TestStatic);
</script>
  </dom-module>

  <dom-module id='test-slot'>
    <template>
      <span id="slotContainer">[<slot id="slot"></slot>]</span>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class TestSlot extends PolymerElement {
  static get is() { return 'test-slot'; }
}
customElements.define(TestSlot.is, TestSlot);
</script>
  </dom-module>

  <dom-module id='test-slot1'>
    <template>
      <test-slot id="slot"><slot></slot></test-slot>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class TestSlot1 extends PolymerElement {
  static get is() { return 'test-slot1'; }
}
customElements.define(TestSlot1.is, TestSlot1);
</script>
  </dom-module>

  <dom-module id='test-slot2'>
    <template>
      <test-slot1 id="slot"><slot></slot></test-slot1>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class TestSlot2 extends PolymerElement {
  static get is() { return 'test-slot2'; }
}
customElements.define(TestSlot2.is, TestSlot2);
</script>
  </dom-module>

  <dom-module id='test-slot3'>
    <template>
      <test-slot2 id="slot"><slot></slot></test-slot2>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class TestSlot3 extends PolymerElement {
  static get is() { return 'test-slot3'; }
}
customElements.define(TestSlot3.is, TestSlot3);
</script>
  </dom-module>

  <dom-module id='test-slot-raw'>
    <template>
      <div id="slot"><slot></slot></div>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class TestSlotRaw extends PolymerElement {
  static get is() { return 'test-slot-raw'; }
}
customElements.define(TestSlotRaw.is, TestSlotRaw);
</script>
  </dom-module>

  <dom-module id='test-slot-attr'>
    <template>
      [<slot id="slot" name="d"></slot>]
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class El extends PolymerElement {
  static get is() { return 'test-slot-attr'; }
}
customElements.define(El.is, El);
</script>
  </dom-module>

  <dom-module id='test-slot-attr1'>
    <template>
      <test-slot-attr id="slot"><slot name="c" slot="d"></slot></test-slot-attr>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class El extends PolymerElement {
  static get is() { return 'test-slot-attr1'; }
}
customElements.define(El.is, El);
</script>
  </dom-module>

  <dom-module id='test-slot-attr2'>
    <template>
      <test-slot-attr1 id="slot"><slot name="b" slot="c"></slot></test-slot-attr1>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class El extends PolymerElement {
  static get is() { return 'test-slot-attr2'; }
}
customElements.define(El.is, El);
</script>
  </dom-module>

  <dom-module id='test-slot-attr3'>
    <template>
      <test-slot-attr2 id="slot"><slot name="a" slot="b"></slot></test-slot-attr2>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class El extends PolymerElement {
  static get is() { return 'test-slot-attr3'; }
}
customElements.define(El.is, El);
</script>
  </dom-module>

  <dom-module id='test-slot-attr-inside'>
    <template>
      <test-slot-attr3 id="slot"><slot name="a" slot="a"></slot></test-slot-attr3>
    </template>
    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
import '../../lib/utils/flattened-nodes-observer.js';
class El extends PolymerElement {
  static get is() { return 'test-slot-attr-inside'; }
}
customElements.define(El.is, El);
</script>
  </dom-module>


  <test-slot><div>A</div><div>B</div></test-slot>

  <test-static><div>static A</div><div>static B</div></test-static>

  <div id="staticDiv"></div>

<script type="module">
import '../../polymer-element.js';
import { FlattenedNodesObserver } from '../../lib/utils/flattened-nodes-observer.js';

suite('observeNodes', function() {

  test('observe initial state of distributing element', function() {
    var recordedA;
    var el = document.querySelector('test-slot');
    var observer1 = new FlattenedNodesObserver(el, function(info) {
      recordedA = info;
    });
    observer1.flush();
    assert.equal(recordedA.addedNodes.length, 2);
    recordedA = null;
    var recordedB;
    var observer2 = new FlattenedNodesObserver(el, function(info) {
      recordedB = info;
    });
    observer2.flush();
    assert.equal(recordedA, null);
    assert.equal(recordedB.addedNodes.length, 2);
    observer1.disconnect();
    observer2.disconnect();
  });

  test('observe initial state of non-distributing element', function() {
    var recordedA;
    var el = document.querySelector('test-static');
    var observer1 = new FlattenedNodesObserver(el, function(info) {
      recordedA = info;
    });
    observer1.flush();
    assert.equal(recordedA.addedNodes.length, 2);
    recordedA = null;
    var recordedB;
    var observer2 = new FlattenedNodesObserver(el, function(info) {
      recordedB = info;
    });
    observer2.flush();
    assert.equal(recordedA, null);
    assert.equal(recordedB.addedNodes.length, 2);
    observer1.disconnect();
    observer2.disconnect();
  });

  test('observeNodes called in observed node context', function(done) {
    var el = document.createElement('test-slot');
    document.body.appendChild(el);
    var observer = new FlattenedNodesObserver(el, function() {
      assert.equal(this, el);
      done();
    });
    // add
    var d = document.createElement('div');
    el.appendChild(d);
    observer.flush();
  });

  test('observe children changes to distributing element', function() {
    var el = document.createElement('test-slot');
    document.body.appendChild(el);

    var recorded;
    var observer = new FlattenedNodesObserver(el, function(info) {
      recorded = info;
    });
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.removedNodes.length, 0);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // remove
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 0);
    assert.equal(recorded.removedNodes.length, 2);
    assert.equal(recorded.removedNodes[0], d);
    assert.equal(recorded.removedNodes[1], d1);
    // add
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // reset, unobserve and remove
    recorded = null;
    observer.disconnect();
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded, null);
    document.body.removeChild(el);
  });

  test('observe children changes to distributing element that provoke additional changes', function() {
    var el = document.createElement('test-slot');
    document.body.appendChild(el);

    var recordedInfo, elAddedInObserver;
    var observerCallCount = 0;
    var observer = new FlattenedNodesObserver(el, function(info) {
      observerCallCount++;
      recordedInfo = info;
      if (info.target.childNodes.length < 5) {
        elAddedInObserver = document.createElement('div');
        info.target.appendChild(elAddedInObserver);
     }

    });
    // add
    var d = document.createElement('div');
    el.appendChild(d);
    while (observer.flush()) {
      // re-flush until done
    }
    assert.equal(observerCallCount, 5);
    assert.equal(recordedInfo.addedNodes.length, 1);
    assert.equal(recordedInfo.addedNodes[0], elAddedInObserver);
    assert.equal(el.childNodes.length, 5);
    document.body.removeChild(el);
    observer.disconnect();
  });

  test('observe children changes to distributing element (async)', function(done) {
    var el = document.createElement('test-slot');
    document.body.appendChild(el);

    var nodes = [];
    var handle = new FlattenedNodesObserver(el, function(info) {
      for (var i=0, at; i < info.removedNodes.length; i++) {
        at = nodes.indexOf(info.removedNodes[i]);
        assert.isAbove(at, -1);
        nodes.splice(at, 1);
      }
      nodes = nodes.concat(info.addedNodes);
    });
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    let getFlattenedNodes = FlattenedNodesObserver.getFlattenedNodes;
    setTimeout(function() {
      assert.sameMembers(getFlattenedNodes(el), nodes);
      // remove
      el.removeChild(d);
      el.removeChild(d1);
      setTimeout(function() {
        assert.sameMembers(getFlattenedNodes(el), nodes);
        // add
        el.appendChild(d);
        el.appendChild(d1);
        setTimeout(function() {
          assert.sameMembers(getFlattenedNodes(el), nodes);
          handle.disconnect();
          el.removeChild(d);
          el.removeChild(d1);
          setTimeout(function() {
            assert.notEqual(nodes.length, getFlattenedNodes(el).length);
            document.body.removeChild(el);
            done();
          });
        });
      });
    });
  });

  test('observe children changes to non-distributing element', function() {
    var el = document.createElement('test-static');
    document.body.appendChild(el);

    var recorded;
    var observer = new FlattenedNodesObserver(el, function(info) {
      recorded = info;
    });
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.removedNodes.length, 0);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // remove
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 0);
    assert.equal(recorded.removedNodes.length, 2);
    assert.equal(recorded.removedNodes[0], d);
    assert.equal(recorded.removedNodes[1], d1);
    // add
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // reset, unobserve and remove
    recorded = null;
    observer.disconnect();
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded, null);
    document.body.removeChild(el);
  });

  test('observe changes to inner node wrapping <slot>', function() {
    var el = document.createElement('test-slot');
    document.body.appendChild(el);

    var observedInfo;
    var observer = new FlattenedNodesObserver(el.$.slotContainer, function(info) {
      observedInfo = info;
    });
    observer.flush();
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(observedInfo.target, el.$.slotContainer);
    assert.equal(observedInfo.addedNodes.length, 2);
    assert.equal(observedInfo.removedNodes.length, 0);
    // remove
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(observedInfo.addedNodes.length, 0);
    assert.equal(observedInfo.removedNodes.length, 2);
    // add
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(observedInfo.addedNodes.length, 2);
    assert.equal(observedInfo.removedNodes.length, 0);
    // reset, unobserve and remove
    observedInfo = null;
    observer.disconnect();
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(observedInfo, null);
    document.body.removeChild(el);
  });

  test('observe changes to <slot>', function() {
    var el = document.createElement('test-slot');
    document.body.appendChild(el);

    var observedInfo;
    var observer = new FlattenedNodesObserver(el.$.slot, function(info) {
      observedInfo = info;
    });
    observer.flush();
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(observedInfo.target, el.$.slot);
    assert.equal(observedInfo.addedNodes.length, 2);
    assert.equal(observedInfo.removedNodes.length, 0);
    // remove
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(observedInfo.addedNodes.length, 0);
    assert.equal(observedInfo.removedNodes.length, 2);
    // add
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(observedInfo.addedNodes.length, 2);
    assert.equal(observedInfo.removedNodes.length, 0);
    // reset, unobserve and remove
    observedInfo = null;
    observer.disconnect();
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(observedInfo, null);
    document.body.removeChild(el);
  });

  test('observe effective children inside distributing element', function() {
    var el = document.createElement('test-slot1');
    document.body.appendChild(el);

    var recorded;
    var observer = new FlattenedNodesObserver(el.$.slot, function(info) {
      recorded = info;
    });
    observer.flush();
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.removedNodes.length, 0);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // remove
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 0);
    assert.equal(recorded.removedNodes.length, 2);
    assert.equal(recorded.removedNodes[0], d);
    assert.equal(recorded.removedNodes[1], d1);
    // add
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // reset, unobserve and remove
    recorded = null;
    observer.disconnect();
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded, null);
    document.body.removeChild(el);
  });

  test('observe effective children changes when adding to another host', function() {
    var el = document.createElement('test-slot1');
    document.body.appendChild(el);

    var recorded;
    var observer = new FlattenedNodesObserver(el.$.slot, function(info) {
      recorded = info;
    });
    observer.flush();
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.removedNodes.length, 0);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // add somewhere else... we should see these as removes
    document.body.appendChild(d);
    document.body.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 0);
    assert.equal(recorded.removedNodes.length, 2);
    assert.equal(recorded.removedNodes[0], d);
    assert.equal(recorded.removedNodes[1], d1);
    // cleanup
    document.body.removeChild(d);
    document.body.removeChild(d1);
    document.body.removeChild(el);
    observer.disconnect();
  });

  test('observe effective children changes in static slot when adding to another host', function() {
    var el = document.createElement('staticDiv');
    document.body.appendChild(el);

    var recorded;
    var observer = new FlattenedNodesObserver(el, function(info) {
      recorded = info;
    });
    observer.flush();
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.removedNodes.length, 0);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // add somewhere else... we should see these as removes
    document.body.appendChild(d);
    document.body.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 0);
    assert.equal(recorded.removedNodes.length, 2);
    assert.equal(recorded.removedNodes[0], d);
    assert.equal(recorded.removedNodes[1], d1);
    // cleanup
    document.body.removeChild(d);
    document.body.removeChild(d1);
    document.body.removeChild(el);
    observer.disconnect();
  });

  test('observe effective children inside deep distributing element', function() {
    var el = document.createElement('test-slot3');
    document.body.appendChild(el);

    var recorded;
    var slot = el.$.slot.$.slot.$.slot;
    var observer = new FlattenedNodesObserver(slot, function(info) {
      recorded = info;
    });
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.removedNodes.length, 0);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // remove
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 0);
    assert.equal(recorded.removedNodes.length, 2);
    assert.equal(recorded.removedNodes[0], d);
    assert.equal(recorded.removedNodes[1], d1);
    // add
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // reset, unobserve and remove
    recorded = null;
    observer.disconnect();
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded, null);
    document.body.removeChild(el);
  });

  test('observe <slot> inside deep distributing element', function() {
    var el = document.createElement('test-slot3');
    document.body.appendChild(el);

    var recorded;
    var slot = el.$.slot.$.slot.$.slot.$.slot;
    assert.equal(slot.localName, 'slot');
    var observer = new FlattenedNodesObserver(slot, function(info) {
      recorded = info;
    });
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.removedNodes.length, 0);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // remove
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 0);
    assert.equal(recorded.removedNodes.length, 2);
    assert.equal(recorded.removedNodes[0], d);
    assert.equal(recorded.removedNodes[1], d1);
    // add
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 2);
    assert.equal(recorded.addedNodes[0], d);
    assert.equal(recorded.addedNodes[1], d1);
    // reset, unobserve and remove
    recorded = null;
    observer.disconnect();
    el.removeChild(d);
    el.removeChild(d1);
    observer.flush();
    assert.equal(recorded, null);
    document.body.removeChild(el);
  });

  test('observe effective children inside deep distributing element (async)', function(done) {
    var el = document.createElement('test-slot3');
    document.body.appendChild(el);

    var recorded;
    var slot = el.$.slot.$.slot.$.slot;
    var observer = new FlattenedNodesObserver(slot, function(info) {
      recorded = info;
    });
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    setTimeout(function() {
      assert.equal(recorded.addedNodes.length, 2);
      assert.equal(recorded.removedNodes.length, 0);
      assert.equal(recorded.addedNodes[0], d);
      assert.equal(recorded.addedNodes[1], d1);
      // remove
      el.removeChild(d);
      el.removeChild(d1);
      setTimeout(function() {
        assert.equal(recorded.addedNodes.length, 0);
        assert.equal(recorded.removedNodes.length, 2);
        assert.equal(recorded.removedNodes[0], d);
        assert.equal(recorded.removedNodes[1], d1);
        // add
        el.appendChild(d);
        el.appendChild(d1);
        setTimeout(function() {
          assert.equal(recorded.addedNodes.length, 2);
          assert.equal(recorded.addedNodes[0], d);
          assert.equal(recorded.addedNodes[1], d1);
          // reset, unobserve and remove
          recorded = null;
          observer.disconnect();
          el.removeChild(d);
          el.removeChild(d1);
          setTimeout(function() {
            assert.equal(recorded, null);
            document.body.removeChild(el);
            done();
          });
        });
      });
    });
  });


  test('observe effective children attr changes inside deep distributing element (async)', function(done) {
    var el = document.createElement('test-slot-attr3');
    document.body.appendChild(el);

    var recorded;
    var slot = el.$.slot.$.slot.$.slot;
    var observer = new FlattenedNodesObserver(slot, function(info) {
      recorded = info;
    });
    observer.flush();
    recorded = null;
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded, null);
    setTimeout(function() {
      assert.equal(recorded, null);
      d.setAttribute('slot', 'a');
      setTimeout(function() {
        assert.equal(recorded.addedNodes.length, 1);
        assert.equal(recorded.removedNodes.length, 0);
        assert.equal(recorded.addedNodes[0], d);
        d.removeAttribute('slot');
        setTimeout(function() {
          assert.equal(recorded.addedNodes.length, 0);
          assert.equal(recorded.removedNodes.length, 1);
          assert.equal(recorded.removedNodes[0], d);
          recorded = null;
          observer.disconnect();
          d.setAttribute('slot', 'a');
          setTimeout(function() {
            assert.equal(recorded, null);
            document.body.removeChild(el);
            done();
          });
        });
      });
    });
  });

  test('observe effective children attr changes inside deep distributing element without outer select (async)', function(done) {
    var el = document.createElement('test-slot-attr-inside');
    document.body.appendChild(el);

    var recorded;
    var slot = el.$.slot.$.slot.$.slot.$.slot;
    var observer = new FlattenedNodesObserver(slot, function(info) {
      recorded = info;
    });
    observer.flush();
    recorded = null;
    // add
    var d = document.createElement('div');
    var d1 = document.createElement('div');
    el.appendChild(d);
    el.appendChild(d1);
    observer.flush();
    assert.equal(recorded, null);
    setTimeout(function() {
      assert.equal(recorded, null);
      d.setAttribute('slot', 'a');
      setTimeout(function() {
        assert.equal(recorded.addedNodes.length, 1);
        assert.equal(recorded.removedNodes.length, 0);
        assert.equal(recorded.addedNodes[0], d);
        d.removeAttribute('slot');
        setTimeout(function() {
          assert.equal(recorded.addedNodes.length, 0);
          assert.equal(recorded.removedNodes.length, 1);
          assert.equal(recorded.removedNodes[0], d);
          recorded = null;
          observer.disconnect();
          d.setAttribute('slot', 'a');
          setTimeout(function() {
            assert.equal(recorded, null);
            document.body.removeChild(el);
            done();
          });
        });
      });
    });
  });

  test('add/remove multiple observers', function() {
    var el = document.createElement('test-slot1');
    document.body.appendChild(el);

    var r1 = 0;
    var h1 = new FlattenedNodesObserver(el.$.slot, function() {
      r1++;
    });
    var r2 = 0;
    var h2 = new FlattenedNodesObserver(el.$.slot, function() {
      r2++;
    });
    var r3 = 0;
    var h3 = new FlattenedNodesObserver(el.$.slot, function() {
      r3++;
    });
    // add
    var d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 1);
    assert.equal(r2, 1);
    assert.equal(r3, 1);
    h1.disconnect();
    d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 1);
    assert.equal(r2, 2);
    assert.equal(r3, 2);
    h2.disconnect();
    d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 1);
    assert.equal(r2, 2);
    assert.equal(r3, 3);
    h3.disconnect();
    d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 1);
    assert.equal(r2, 2);
    assert.equal(r3, 3);
    h1 = new FlattenedNodesObserver(el.$.slot, h1.callback);
    h2 = new FlattenedNodesObserver(el.$.slot, h2.callback);
    h3 = new FlattenedNodesObserver(el.$.slot, h3.callback);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 2);
    assert.equal(r2, 3);
    assert.equal(r3, 4);
    d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 3);
    assert.equal(r2, 4);
    assert.equal(r3, 5);
    h3.disconnect();
    d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 4);
    assert.equal(r2, 5);
    assert.equal(r3, 5);
    h2.disconnect();
    d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 5);
    assert.equal(r2, 5);
    assert.equal(r3, 5);
    h1.disconnect();
    d = document.createElement('div');
    el.appendChild(d);
    h1.flush();
    h2.flush();
    h3.flush();
    assert.equal(r1, 5);
    assert.equal(r2, 5);
    assert.equal(r3, 5);
  });

  test('observe changes of target with dynamically added <slot>', function() {
    var el = document.createElement('test-static');
    document.body.appendChild(el);
    var container = el.shadowRoot.childNodes[1];
    var recorded = null;
    var observer = new FlattenedNodesObserver(container, function(info) {
      recorded = info;
    });
    var slot = document.createElement('slot');
    container.appendChild(slot);
    observer.flush();
    recorded = null;
    var child = document.createElement('div');
    el.appendChild(child);
    observer.flush();
    assert.equal(recorded.addedNodes.length, 1);
    assert.equal(recorded.addedNodes[0], child);
    recorded = null;
    el.removeChild(child);
    observer.flush();
    assert.equal(recorded.removedNodes.length, 1);
    assert.equal(recorded.removedNodes[0], child);
    recorded = null;
    container.removeChild(slot);
    observer.flush();
    el.appendChild(child);
    assert.equal(recorded, null);
    observer.disconnect();
    document.body.removeChild(el);
  });

  test('observe changes of target with dynamically added <slot> (flush/async)', function(done) {
    var el = document.createElement('test-static');
    document.body.appendChild(el);
    var container = el.shadowRoot.childNodes[1];
    var recorded = null;
    var observer = new FlattenedNodesObserver(container, function(info) {
      recorded = info;
    });
    var slot = document.createElement('slot');
    container.appendChild(slot);
    observer.flush();
    recorded = null;
    var child = document.createElement('div');
    el.appendChild(child);
    setTimeout(function() {
      assert.equal(recorded.addedNodes.length, 1);
      assert.equal(recorded.addedNodes[0], child);
      done();
    });
  });

  test('observe changes of target with dynamically added <slot> (async)', function(done) {
    var el = document.createElement('test-static');
    document.body.appendChild(el);
    var container = el.shadowRoot.childNodes[1];
    var recorded = null;
    var observer = new FlattenedNodesObserver(container, function(info) {
      recorded = info;
    });
    var slot = document.createElement('slot');
    container.appendChild(slot);
    observer.flush();
    recorded = null;
    var child = document.createElement('div');
    el.appendChild(child);
    setTimeout(function() {
      assert.equal(recorded.addedNodes.length, 1);
      assert.equal(recorded.addedNodes[0], child);
      recorded = null;
      el.removeChild(child);
      setTimeout(function() {
        assert.equal(recorded.removedNodes.length, 1);
        assert.equal(recorded.removedNodes[0], child);
        recorded = null;
        container.removeChild(slot);
        setTimeout(function() {
          el.appendChild(child);
          assert.equal(recorded, null);
          observer.disconnect();
          document.body.removeChild(el);
          done();
        });
      });
    });
  });

  test('element distributed to insertion point observing itself', function() {
    var host = document.createElement('test-slot');
    var el = document.createElement('test-self-observe');
    host.appendChild(el);
    document.body.appendChild(host);
    if (window.ShadyDOM) {
      ShadyDOM.flush();
    }
    assert.ok(el._observer);
    var div = document.createElement('div');
    el.appendChild(div);
    el._observer.flush();
    assert.equal(el.info.addedNodes[0], div);
    document.body.removeChild(host);
  });

  test('should not fail on node without children', function() {
    var recorded;
    var el = document;
    var observer = new FlattenedNodesObserver(el, function(info) {
      recorded = info;
    });
    assert.equal(recorded, null);
    observer.disconnect();
  });

});
</script>

</body>
</html>
